package com.simba.server;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import com.simba.common.command.SQLCommandExecutor;
import com.simba.server.components.*;
import com.simba.server.task.*;
import org.apache.log4j.Logger;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.input.SAXBuilder;
import org.jdom2.input.sax.XMLReaderSAX2Factory;
import com.simba.GlobalParam;
import com.simba.common.command.CommandParams;

import com.simba.common.database.DbPool;
import com.simba.common.database.DbPoolInterface;
import com.simba.common.frameserver.FrameChannelHandler;
import com.simba.common.frameserver.FrameChannelManager;
import com.simba.common.frameserver.TwoBFrameServer;
import com.simba.common.jsonserver.JsonChannelHandler;
import com.simba.common.jsonserver.JsonChannelManager;
import com.simba.common.jsonserver.JsonServer;
import com.simba.common.log.Log4jLogger;
import com.simba.common.log.LogTask;
import com.simba.common.log.LogUtil;
import com.simba.common.log.LoggerExecutor;
import com.simba.common.processor.FrameProcessor;
import com.simba.common.processor.JsonProcessor;
import com.simba.common.processor.MessageProcessor;
import com.simba.server.framedecoder.FrameDecoderManager;
import com.simba.server.jsonservice.JsonServiceManager;
import com.simba.server.webservice.WebServiceManager;

public class ServerManager
{
	private static ServerManager instance = new ServerManager();
	private static final String LOG_HEADERS_CONFIG_FILE = "simba-header-config.xml";
	private static final String LOG4J_CONFIG_FILE_DEBUG = "simba-log4j-debug.xml";
	private static final String LOG4J_CONFIG_FILE_VERBOSE = "simba-log4j-verbose.xml";
	private static final String MYSQL_DATABASE_CONFIG_FILE = "simba-mysql-config.xml";
	private static final String PLC_CROSS_CONFIG_FILE = "simba-plc-config.xml";

	private static final Logger logger = Logger.getLogger(ServerManager.class);
	public static ServerManager getInstance()
	{
		return ServerManager.instance;
	}

	private final DataClearTask dataClearTask = new DataClearTask();
	private final DataRestoreTask dataRestoreTask = new DataRestoreTask();
	private final DataSyncTask dataSyncTask = new DataSyncTask();
	private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	private TwoBFrameServer frameServer;
	private JsonServer jsonServer;
	private final List<String> logHeaders = new LinkedList<>();
	private final DbPoolInterface mysqlDbPool = new DbPool(ServerManager.MYSQL_DATABASE_CONFIG_FILE, "mysql");
	private final String privateKey = "Post-Sorter-Server-2018-09-01";
	private String runMode;
	private final RecordScanMessageTask scanMessageTask = new RecordScanMessageTask();
	private final UpdatePacketExpiredTask packetExpiredTask = new UpdatePacketExpiredTask();
	private int serialNumber;
	private final BoxPackOfferTask slotPackOfferTask = new BoxPackOfferTask();
	private final SorterStatusTask sorterStatusTask = new SorterStatusTask();
	private final SortResultOfferTask sortResultOfferTask = new SortResultOfferTask();
	private final ClearOverTimeInfoThread clearOldDBTask = new ClearOverTimeInfoThread();
	private final KeepAliveThread keepAliveTask = new KeepAliveThread();

	private ServerManager()
	{
	}

	public DataRestoreTask getDataRestoreTask()
	{
		return this.dataRestoreTask;
	}

	public SimpleDateFormat getDateformat()
	{
		return this.dateFormat;
	}

	public TwoBFrameServer getFrameServer()
	{
		return this.frameServer;
	}

	public String getPrivatekey()
	{
		return this.privateKey;
	}

	public String getRunMode()
	{
		return this.runMode;
	}

	public RecordScanMessageTask getScanMessageTask()
	{
		return scanMessageTask;
	}

	public UpdatePacketExpiredTask getPacketExpiredTask() {
		return packetExpiredTask;
	}

	public int getSerialNumber()
	{
		return this.serialNumber;
	}

	public Document getXmlDocFromFile(String path) throws Exception
	{
		Document xmlDoc;
		try
		{
			SAXBuilder builder = new SAXBuilder();
			xmlDoc = builder.build(path);
		}
		catch (Exception ex)
		{
			throw ex;
		}
		return xmlDoc;
	}

	public void init()
	{
		this.initParam();
		this.initLogHeaders();
		this.initDatabase();
		this.initTransports();
		this.initServerInstance();
		this.initThreadTasks();

		ServerParam.printStartedInfo();
		this.postInitProcess();
	}

	public void setFrameMessageServer(TwoBFrameServer frameSorterServer)
	{
		this.frameServer = frameSorterServer;
	}

	public void setRunMode(String runMode)
	{
		this.runMode = runMode;
	}

	public void setSerialNumber(int serialNumber)
	{
		this.serialNumber = serialNumber;
	}

	protected void initDbPool()
	{
		this.mysqlDbPool.init(true);
		SQLCommandExecutor.getInstance().setDbPool(this.mysqlDbPool);
	}

	protected void initParam()
	{
		System.setProperty("java.net.preferIPv4Stack", "true");
		if (this.runMode.equals("debug"))
		{
			ServerParam.initRootLogger(ServerManager.LOG4J_CONFIG_FILE_DEBUG);
		}
		else
		{
			ServerParam.initRootLogger(ServerManager.LOG4J_CONFIG_FILE_VERBOSE);
		}
		ServerParam.initGlobalConfiguration();
	}

	protected void initTransports()
	{
		this.buildFrameServer();
		this.buildJsonServer();
	}

	private void buildFrameServer()
	{
		System.out.println("[Server Init] Starting the tcp server of frame message ...");
		try
		{
			final FrameProcessor frameMessageProcessor = new FrameProcessor(
					FrameDecoderManager.getInstance(), FrameChannelManager.getInstance());
			final FrameChannelHandler handler = new FrameChannelHandler(frameMessageProcessor);

			this.setFrameMessageServer(new TwoBFrameServer(
					ServerParam.getServerInfo().getDefaultServerAddress(),
					ServerParam.getServerInfo().getDefaultServerPort(),
					ServerParam.IO_THREAD_POOL_SIZE, ServerParam.EVENT_THREAD_POOL_SIZE, handler));

			frameMessageProcessor.setLogHeaders(this.logHeaders);
			for (String header : this.logHeaders)
			{
				FrameChannelManager.getInstance().addHeader(header);
			}
			this.getFrameServer().start();

			final String printInfo = "[Server Init] Starting the frame sorter server on: "
					+ this.getFrameServer().getInetSocketAddress().toString();
			LogUtil.LogInfo(ServerManager.logger, printInfo);
		}
		catch (final Exception e)
		{
			LogUtil.LogError(ServerManager.logger, e.toString());
		}
	}

	private void buildJsonServer()
	{
		System.out.println("[Server Init] Starting the json server for service ...");
		try
		{
			final MessageProcessor jsonMessageProcessor = new JsonProcessor(
					JsonServiceManager.getInstance(), JsonChannelManager.getInstance());
			final JsonChannelHandler handler = new JsonChannelHandler(jsonMessageProcessor);

			this.jsonServer = new JsonServer(ServerParam.getServerInfo().getJsonServerAddress(),
					ServerParam.getServerInfo().getJsonServerPort(),
					ServerParam.IO_THREAD_POOL_SIZE, ServerParam.EVENT_THREAD_POOL_SIZE, handler);

			this.jsonServer.start();

			final String printInfo = "[Server Init】Starting the json server on: "
					+ this.jsonServer.getInetSocketAddress().toString();
			LogUtil.LogInfo(ServerManager.logger, printInfo);
		}
		catch (final Exception e)
		{
			LogUtil.LogError(ServerManager.logger, e.toString());
		}
	}

	private void initBarcodeManager()
	{
		BarcodeManager.getInstance().init();
	}

	private void initCommandParams()
	{
		CommandParams.getInstance().init();
	}

	private void initDatabase()
	{
		this.initDbPool();
	}

	private void initFrameDecodeManager()
	{
		FrameDecoderManager.getInstance().init();
	}

	private void initJsonServiceManager()
	{
		JsonServiceManager.getInstance().init();
	}

	private void initLogHeaders()
	{
		Document rootDoc;

		try
		{
			// rootDoc = ServerManager.getInstance().getXmlDocFromFile(new
			// File("etc" + GlobalParam.PATH_SEP +
			// ServerManager.LOG_HEADERS_CONFIG_FILE));
			rootDoc = ServerManager.getInstance().getXmlDocFromFile(
					"etc" + GlobalParam.PATH_SEP + ServerManager.LOG_HEADERS_CONFIG_FILE);

			Element root = rootDoc.getRootElement();
			Element headersElement = root.getChild("Headers");

			java.util.List<Element> headers = headersElement.getChildren();

			for (Element element : headers)
			{
				String value = element.getAttributeValue("value");
				this.logHeaders.add(value);
			}
		}
		catch (Exception ex)
		{
			LoggerExecutor.getInstance().execute(new LogTask(new Log4jLogger(ServerManager.logger),
					"Send headers init error : " + ex.getMessage(), LogTask.ERROR));
		}
	}

	private void initMachineParameters()
	{
		ConfigManager.getInstance().loadBasicConfig();
	}


	private void initServerInstance()
	{
		this.initFrameDecodeManager();
		this.initJsonServiceManager();
		this.initWebServiceManager();
		this.initCommandParams();
		this.initSorterParameters();
		this.initPrinterManager();
		this.initMachineParameters();
		this.initSorterManager();
		this.initSlotManager();
		this.initSortControllerManager();
		this.initSortCommandManager();
		this.initBarcodeManager();

	}

	private void initSorterManager(){
		SorterManager.getInstance().init(PLC_CROSS_CONFIG_FILE);
	}

	private void initSlotManager()
	{
		SlotManager.getInstance().init();
	}

	private void initSortCommandManager()
	{
		SortProtocolCreator.getInstance().init();
	}

	private void initSortControllerManager()
	{
		SortControllerManager.getInstance().init();
	}

	private void initSorterParameters()
	{
		SorterParams.getInstance().init();
	}

	private void initThreadTasks()
	{
		this.dataClearTask.start();
		this.dataSyncTask.start();
		this.dataRestoreTask.start();
		this.slotPackOfferTask.start();
		this.sortResultOfferTask.start();
		this.sorterStatusTask.start();
		this.scanMessageTask.start();
		this.packetExpiredTask.start();
		this.keepAliveTask.start();
		this.clearOldDBTask.start();
	}

	private void initWebServiceManager()
	{
		WebServiceManager.getInstance().init();
	}

	private void postInitProcess()
	{
		FrameDecoderManager.getInstance().postInitProcess();
		JsonServiceManager.getInstance().postInitProcess();
		SlotManager.getInstance().postInitProcess();
		// SortInfoManager.getInstance().postInitProcess();
		this.whenInitProcessEnd();
	}

	private void initPrinterManager()
	{
		PrinterInfoManager.getInstance().loadPrinters();
	}

	private void whenInitProcessEnd()
	{
	}
}
